home *** CD-ROM | disk | FTP | other *** search
/ Enter 2006 September / Enter 09 2006.iso / Internet / SpamExperts Home 1.1 / SpamExperts Home.exe / lib / spamexperts.modules / spamexperts / software_update.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2006-07-14  |  31.3 KB  |  1,001 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.4)
  3.  
  4. '''
  5. This module provides functionality to auto update the SpamExperts software.
  6.  
  7. This includes updating the spamexperts.modules file that contains
  8. all the .pyc files that SpamExperts uses.  The idea is that this file
  9. should be updated rather than replaced, as it is just a zipped collection
  10. of small files, the vast majority of which will not change during an
  11. update.  Only needing to download those files that have changed will
  12. dramatically increase the speed of updates.
  13. '''
  14. from __future__ import division
  15. import os
  16. import re
  17. import md5
  18. import sys
  19. import types
  20. import errno
  21. import shutil
  22. import socket
  23. import urllib
  24. import gettext
  25. import marshal
  26. import zipfile
  27. import _winreg
  28. import urllib2
  29. import fnmatch
  30. import winerror
  31. import platform
  32. import urlparse
  33. import tempfile
  34. import subprocess
  35.  
  36. try:
  37.     import cStringIO as StringIO
  38. except ImportError:
  39.     import StringIO
  40.  
  41.  
  42. try:
  43.     WindowsError
  44. except NameError:
  45.     WindowsError = Exception
  46.  
  47. import win32api
  48. import win32event
  49. import pywintypes
  50.  
  51. try:
  52.     import ntsecuritycon
  53. except ImportError:
  54.     ntsecuritycon = None
  55.  
  56.  
  57. try:
  58.     import win32security
  59. except ImportError:
  60.     win32security = None
  61.  
  62. from spamexperts import dis
  63. from spamexperts.Options import options
  64. from spamexperts.license import get_code
  65. from spamexperts.Version import versions
  66. from spamexperts.LSPControl import LSPControl
  67. from spamexperts.resources import application_directory
  68.  
  69. try:
  70.     _
  71. except NameError:
  72.     _ = gettext.gettext
  73.  
  74.  
  75. def AdjustPrivilege(priv, enable = True):
  76.     if ntsecuritycon is None or win32security is None:
  77.         raise pywintypes.error()
  78.     
  79.     flags = ntsecuritycon.TOKEN_ADJUST_PRIVILEGES | ntsecuritycon.TOKEN_QUERY
  80.     htoken = win32security.OpenProcessToken(win32api.GetCurrentProcess(), flags)
  81.     id = win32security.LookupPrivilegeValue(None, priv)
  82.     if enable:
  83.         newPrivileges = [
  84.             (id, ntsecuritycon.SE_PRIVILEGE_ENABLED)]
  85.     else:
  86.         newPrivileges = [
  87.             (id, 0)]
  88.     win32security.AdjustTokenPrivileges(htoken, 0, newPrivileges)
  89.  
  90.  
  91. def NTReboot(message, timeout, bForce = False, bReboot = True):
  92.     if ntsecuritycon is None or win32security is None:
  93.         raise pywintypes.error()
  94.     
  95.     AdjustPrivilege(ntsecuritycon.SE_SHUTDOWN_NAME)
  96.     
  97.     try:
  98.         win32api.InitiateSystemShutdown(None, message, timeout, bForce, bReboot)
  99.     finally:
  100.         AdjustPrivilege(ntsecuritycon.SE_SHUTDOWN_NAME, False)
  101.  
  102.  
  103.  
  104. def Reboot():
  105.     
  106.     try:
  107.         NTReboot('', 0)
  108.     except pywintypes.error:
  109.         platform = platform.release()
  110.         if platform in ('95', '98', 'ME'):
  111.             subprocess.Popen('rundll32.exe shell32.dll,SHExitWindowsEx 2')
  112.         elif platform in ('NT', '2K'):
  113.             subprocess.Popen('shutdown.exe /L /R')
  114.         elif platform in ('XP', '2003'):
  115.             subprocess.Popen('shutdown.exe -r')
  116.         else:
  117.             print >>sys.stderr, 'Unsupported Windows version.'
  118.     except:
  119.         platform in ('95', '98', 'ME')
  120.  
  121.  
  122.  
  123. class VersionControl(object):
  124.     '''This class provides the auto update functionality for an
  125.     application.'''
  126.     
  127.     def getVersionFromServer(self):
  128.         '''Get the current version from the update server.
  129.  
  130.         Returns the version or None on error.
  131.         '''
  132.         timeout = socket.getdefaulttimeout()
  133.         socket.setdefaulttimeout(15)
  134.         if get_code():
  135.             url = 'http://%s/%s' % (options[('globals', 'professional_update_server')], options[('globals', 'software_version_file')])
  136.         else:
  137.             url = 'http://%s/%s' % (options[('globals', 'software_update_server')], options[('globals', 'software_version_file')])
  138.         
  139.         try:
  140.             data = urllib2.urlopen(url).read()
  141.         except (urllib2.HTTPError, urllib2.URLError, socket.error):
  142.             e = None
  143.             print >>sys.stderr, 'Error checking for update:', e
  144.             data = None
  145.  
  146.         socket.setdefaulttimeout(timeout)
  147.         return data
  148.  
  149.     
  150.     def getVersionFromRegistry(self):
  151.         '''Get the installed version from the registry.
  152.  
  153.         Returns the version or None on error.
  154.         '''
  155.         key = _winreg.CreateKey(_winreg.HKEY_LOCAL_MACHINE, 'Software\\SpamExperts')
  156.         
  157.         try:
  158.             return _winreg.QueryValueEx(key, 'LatestBuild')[0]
  159.         except WindowsError:
  160.             return None
  161.  
  162.  
  163.     
  164.     def checkForUpdate(self):
  165.         '''Check for an update.
  166.         
  167.         Returns True if an update is available, false otherwise.
  168.         '''
  169.         available_version = self.getVersionFromServer()
  170.         if available_version:
  171.             release_date = float(available_version)
  172.             if release_date < 1142370460:
  173.                 return False
  174.             
  175.             current_version = self.getVersionFromRegistry()
  176.             if current_version is None:
  177.                 return False
  178.             
  179.             
  180.             try:
  181.                 available_version_number = float(available_version)
  182.                 current_version_number = float(current_version)
  183.             except ValueError:
  184.                 return False
  185.  
  186.             if available_version_number < current_version_number:
  187.                 print >>sys.stderr, 'Available version is older than current.'
  188.                 return False
  189.             elif available_version_number == current_version_number:
  190.                 if options[('globals', 'verbose')]:
  191.                     print 'Using most recent version.'
  192.                 
  193.                 return False
  194.             
  195.             return True
  196.         
  197.         return False
  198.  
  199.     
  200.     def doUpdate(self):
  201.         '''Perform an update.'''
  202.         return subprocess.Popen(options[('globals', 'software_updater')])
  203.  
  204.  
  205.  
  206. class Update(object):
  207.     '''Updater base class.  Must be subclassed to be used.'''
  208.     
  209.     def __init__(self, exe_dir, force = False):
  210.         self.temp_dir = os.path.join(win32api.GetTempPath(), 'spamexperts_update')
  211.         self.backup_dir = os.path.join(self.temp_dir, 'backup')
  212.         
  213.         try:
  214.             os.makedirs(self.backup_dir)
  215.         except OSError:
  216.             pass
  217.  
  218.         self.exe_dir = exe_dir
  219.         self.force = force
  220.         self.files_to_replace = []
  221.         self.replace_on_restart = []
  222.         self.backup_files = []
  223.  
  224.     
  225.     def all_files(self, directory, files):
  226.         '''Recursively build a list of all files inside the given
  227.         directory, using paths relative to the initial directory.'''
  228.         for fn in os.listdir(directory):
  229.             full_fn = os.path.join(directory, fn)
  230.             if os.path.isdir(full_fn):
  231.                 self.all_files(full_fn, files)
  232.                 continue
  233.             files.append(full_fn)
  234.         
  235.         return files
  236.  
  237.     
  238.     def checksum(fn):
  239.         '''Return a simple checksum of the specified file.'''
  240.         return md5.md5(file(fn, 'rb').read()).hexdigest()
  241.  
  242.     checksum = staticmethod(checksum)
  243.     FAILED = 'f'
  244.     SUCCEEDED = 's'
  245.     RESTART = 'r'
  246.     SUCCEEDED_NO_REPLACE = 'n'
  247.     
  248.     def download_files(self, update_list_filename, callback, directory_name = None):
  249.         '''Replace (if necessary) the files in update_list_filename.
  250.         As the update progresses, call callback with the percentage
  251.         complete and any status messages.'''
  252.         if directory_name is None:
  253.             directory_name = self.exe_dir
  254.         
  255.         total_replaced = 0
  256.         self.files_to_replace = []
  257.         restart_required = False
  258.         if get_code():
  259.             base_url = 'http://' + options[('globals', 'professional_update_server')]
  260.         else:
  261.             base_url = 'http://' + options[('globals', 'software_update_server')]
  262.         all_files = file(update_list_filename).read().split('\n\n')
  263.         total = len(all_files)
  264.         for i, item in enumerate(all_files):
  265.             if options[('globals', 'verbose')]:
  266.                 print 'Starting download of', repr(item)
  267.             
  268.             if not item:
  269.                 continue
  270.             
  271.             
  272.             try:
  273.                 (local, remote, checksum) = item.strip().split('\n')
  274.             except ValueError:
  275.                 return self.FAILED
  276.  
  277.             callback((i / total) * 100, _('Updating %s') % (local,))
  278.             abs_local = os.path.join(directory_name, local)
  279.             if not os.path.exists(abs_local):
  280.                 local_checksum = None
  281.             else:
  282.                 local_checksum = self.checksum(abs_local)
  283.             if local_checksum != checksum or self.force:
  284.                 result = self.download_file(base_url, remote, local, checksum, abs_local)
  285.                 if result == self.FAILED:
  286.                     return self.FAILED
  287.                 elif result == self.RESTART:
  288.                     restart_required = True
  289.                 
  290.                 total_replaced += 1
  291.                 continue
  292.         
  293.         urllib.urlcleanup()
  294.         if restart_required:
  295.             return self.RESTART
  296.         
  297.         if not total_replaced:
  298.             return self.SUCCEEDED_NO_REPLACE
  299.         
  300.         return self.SUCCEEDED
  301.  
  302.     
  303.     def download_file(self, base_url, remote, local, checksum, abs_local):
  304.         remote_file = urlparse.urljoin(base_url, remote)
  305.         local_temp = os.path.join(self.temp_dir, local)
  306.         if os.path.exists(local_temp) and self.checksum(local_temp) == checksum:
  307.             replacement = local_temp
  308.         else:
  309.             
  310.             try:
  311.                 os.makedirs(os.path.dirname(local_temp))
  312.             except OSError:
  313.                 pass
  314.  
  315.             f = file(local_temp, 'wb')
  316.             
  317.             try:
  318.                 url = urllib2.urlopen(remote_file)
  319.                 f.write(url.read())
  320.             except (urllib2.HTTPError, urllib2.URLError, socket.error):
  321.                 e = None
  322.                 if options[('globals', 'verbose')]:
  323.                     print 'Download error', str(e)
  324.                 
  325.                 return self.FAILED
  326.  
  327.             f.close()
  328.             replacement = local_temp
  329.             verify_checksum = self.checksum(replacement)
  330.             if verify_checksum != checksum:
  331.                 if options[('globals', 'verbose')]:
  332.                     print 'Checksum mismatch', remote_file, verify_checksum, checksum
  333.                 
  334.                 return self.FAILED
  335.             
  336.         self.files_to_replace.append((abs_local, replacement))
  337.         if options[('globals', 'verbose')]:
  338.             print 'Downloaded', replacement
  339.         
  340.         return self.SUCCEEDED
  341.  
  342.     
  343.     def replace_files(self):
  344.         result = self.SUCCEEDED
  345.         self.files_to_replace = list(set(self.files_to_replace))
  346.         while True:
  347.             
  348.             try:
  349.                 (old, new) = self.files_to_replace.pop()
  350.             except IndexError:
  351.                 break
  352.  
  353.             backup = os.path.join(self.backup_dir, old)
  354.             
  355.             try:
  356.                 os.remove(backup)
  357.             except OSError:
  358.                 pass
  359.  
  360.             
  361.             try:
  362.                 os.makedirs(os.path.dirname(backup))
  363.             except OSError:
  364.                 pass
  365.  
  366.             if os.path.exists(old):
  367.                 
  368.                 try:
  369.                     os.rename(old, backup)
  370.                     if os.path.exists(old):
  371.                         err = OSError()
  372.                         err.errno = errno.EACCES
  373.                         raise err
  374.                 except OSError:
  375.                     e = None
  376.                     if e.errno == errno.EACCES:
  377.                         self.replace_on_restart.append((old, new))
  378.                         result = self.RESTART
  379.                         if options[('globals', 'verbose')]:
  380.                             print 'Replacing on restart', old
  381.                             continue
  382.                         continue
  383.                     elif options[('globals', 'verbose')]:
  384.                         print 'File replace failed:', str(e), old, new
  385.                     
  386.                     self.files_to_replace.append((old, new))
  387.                     return self.FAILED
  388.  
  389.                 self.backup_files.append((backup, old))
  390.             
  391.             if not os.path.isdir(os.path.dirname(old)):
  392.                 os.makedirs(os.path.dirname(old))
  393.             
  394.             
  395.             try:
  396.                 os.rename(new, old)
  397.             except OSError:
  398.                 e = None
  399.                 print >>sys.stderr, "Couldn't replace %s (%s).  Giving up." % (old, str(e))
  400.                 return self.FAILED
  401.  
  402.             if options[('globals', 'verbose')]:
  403.                 print 'Replaced', old
  404.                 continue
  405.         return result
  406.  
  407.     
  408.     def undo(self):
  409.         while True:
  410.             
  411.             try:
  412.                 (backup, original) = self.backup_files.pop()
  413.             except IndexError:
  414.                 break
  415.  
  416.             
  417.             try:
  418.                 os.remove(original)
  419.             except OSError:
  420.                 pass
  421.  
  422.             os.rename(backup, original)
  423.  
  424.     
  425.     def generate_filelist(self, directory_name, update_list_filename):
  426.         raise NotImplementedError
  427.  
  428.     
  429.     def cleanup(self):
  430.         if os.path.exists(self.temp_dir):
  431.             
  432.             try:
  433.                 shutil.rmtree(self.temp_dir)
  434.             except OSError:
  435.                 e = None
  436.                 print 'Could not clean up', str(e)
  437.             except:
  438.                 None<EXCEPTION MATCH>OSError
  439.             
  440.  
  441.         None<EXCEPTION MATCH>OSError
  442.  
  443.  
  444.  
  445. class UpdateModules(Update):
  446.     """Update the spamexperts.modules zip file that contains all the
  447.     byte-compiled Python modules that the application uses.
  448.     
  449.     Files can't be removed from a zip file.  We could simply add the new
  450.     files to the zip, and these will be the ones that are imported, but
  451.     that relies on an implementation detail and means that the zip file
  452.     will continually increase in size.  What we do instead is unzip the
  453.     modules file to a temporary location, replace the files that need
  454.     replacing, and then zip it up again."""
  455.     
  456.     def __init__(self, exe_dir, force = False):
  457.         Update.__init__(self, exe_dir, force)
  458.         self.zip_dir = os.path.join(self.temp_dir, 'z')
  459.  
  460.     
  461.     def _disassemble(codeobject):
  462.         dis_output = StringIO.StringIO()
  463.         dis.dis(codeobject, dis_output)
  464.         dis_output.seek(0)
  465.         return dis_output.read()
  466.  
  467.     _disassemble = staticmethod(_disassemble)
  468.     address_re = re.compile('<.*[0-9a-zA-Z]+>', re.DOTALL)
  469.     
  470.     def _get_code_from_object(cls, codeobject):
  471.         code = set()
  472.         code.add(cls.address_re.sub('', cls._disassemble(codeobject)))
  473.         for item in codeobject.co_consts:
  474.             if isinstance(item, types.CodeType):
  475.                 code.add(cls._get_code_from_object(item))
  476.                 continue
  477.             code.add(cls.address_re.sub('', repr(item)))
  478.         
  479.         return ''.join(code)
  480.  
  481.     _get_code_from_object = classmethod(_get_code_from_object)
  482.     
  483.     def old_get_code_from_object(class_, codeobject):
  484.         code = []
  485.         for item in codeobject.co_consts:
  486.             if isinstance(item, types.CodeType):
  487.                 code.append(class_._get_code_from_object(item))
  488.                 continue
  489.             code.append(class_.address_re.sub('', repr(item)))
  490.         
  491.         return ''.join(code)
  492.  
  493.     old_get_code_from_object = classmethod(old_get_code_from_object)
  494.     
  495.     def checksum(cls, fn):
  496.         pyc_codeobject = marshal.loads(file(fn, 'rb').read()[8:])
  497.         code = cls._get_code_from_object(pyc_codeobject)
  498.         unique_string = '%s%s%s' % (code, repr(pyc_codeobject.co_names), pyc_codeobject.co_code)
  499.         return md5.md5(unique_string).hexdigest()
  500.  
  501.     checksum = classmethod(checksum)
  502.     
  503.     def unzip(self, zip_fn):
  504.         '''Unzip the given zipfile to the temp directory.'''
  505.         zip = zipfile.ZipFile(zip_fn)
  506.         for each in zip.namelist():
  507.             if not each.endswith('/'):
  508.                 (root, name) = os.path.split(each)
  509.             
  510.             directory = os.path.normpath(os.path.join(self.zip_dir, root))
  511.             if not os.path.isdir(directory):
  512.                 os.makedirs(directory)
  513.             
  514.             file(os.path.join(directory, name), 'wb').write(zip.read(each))
  515.         
  516.  
  517.     
  518.     def zip(self, zip_fn):
  519.         '''Create a zipfile of the temp directory and store it at the
  520.         specified location.'''
  521.         zf = zipfile.ZipFile(zip_fn, 'w')
  522.         f = self.all_files(self.zip_dir, [])
  523.         offset = len(self.zip_dir) + 1
  524.         for fn in f:
  525.             zf.write(fn, fn[offset:])
  526.         
  527.         zf.close()
  528.  
  529.     
  530.     def generate_filelist(self, zip_fn, update_list_filename):
  531.         if os.path.exists(self.zip_dir):
  532.             shutil.rmtree(self.zip_dir)
  533.         
  534.         self.unzip(zip_fn)
  535.         f = self.all_files(self.zip_dir, [])
  536.         output = file(update_list_filename, 'w')
  537.         offset = len(self.zip_dir) + 1
  538.         for fn in f:
  539.             base = fn[offset:]
  540.             output.write('\n'.join((base, 'dist/modules/' + base.replace('\\', '/'), self.checksum(fn), '', '')))
  541.         
  542.         output.close()
  543.  
  544.     
  545.     def download_files(self, update_list_filename, callback, zip_fn):
  546.         
  547.         local_callback = lambda i, s: callback(10 + i * 0.80000000000000004, s)
  548.         callback(0, _('Decompressing spamexperts.modules'))
  549.         self.unzip(zip_fn)
  550.         self.exe_dir = self.zip_dir
  551.         result = Update.download_files(self, update_list_filename, local_callback)
  552.         if result != self.SUCCEEDED:
  553.             return result
  554.         
  555.         result = self.replace_files()
  556.         if result != self.SUCCEEDED:
  557.             if options[('globals', 'verbose')]:
  558.                 print 'Not replacing spamexperts.modules'
  559.             
  560.             return result
  561.         
  562.         callback(80, _('Recompressing spamexperts.modules'))
  563.         new_name = zip_fn + '.new'
  564.         self.zip(new_name)
  565.         self.files_to_replace.append((zip_fn, new_name))
  566.         callback(100, _('spamexperts.modules update complete'))
  567.         return self.SUCCEEDED
  568.  
  569.  
  570.  
  571. class UpdateGeneral(Update):
  572.     '''Update files outside of zipped modules file.'''
  573.     
  574.     def generate_filelist(self, directory_name, update_list_filename, excludes = ()):
  575.         offset = len(directory_name) + 1
  576.         f = self.all_files(directory_name, [])
  577.         for excluded in excludes:
  578.             f = _[1]
  579.         
  580.         directory_name = directory_name.replace('\\', '/') + '/'
  581.         output = file(update_list_filename, 'w')
  582.         for fn in f:
  583.             base = fn[offset:]
  584.             posix_base = base.replace('\\', '/')
  585.             output.write('\n'.join((base, urlparse.urljoin(directory_name, posix_base), self.checksum(fn), '', '')))
  586.         
  587.         output.close()
  588.  
  589.  
  590.  
  591. def generate_update_files():
  592.     '''Utility function to generate all update files.
  593.  
  594.     Assumes current working directory is py2exe.
  595.     '''
  596.     general = UpdateGeneral(None)
  597.     general.generate_filelist('dist', 'general.txt', (os.path.join('modules', '*'), os.path.join('lib', 'spamexperts.modules'), os.path.join('external_dlls', '*'), os.path.join('lsp', '*')))
  598.     general.generate_filelist(os.path.join('dist', 'lsp'), 'lsps.txt')
  599.     general.generate_filelist(os.path.join('dist', 'external_dlls'), 'sysfiles.txt', ('SpamExpertsLSP.ini',))
  600.     modules = UpdateModules(None)
  601.     modules.generate_filelist(os.path.join('dist', 'lib', 'spamexperts.modules'), 'modules.txt')
  602.  
  603.  
  604. class CompleteUpdate(object):
  605.     title = versions['Apps']['SpamExperts']['Description']
  606.     name = title + _('Update')
  607.     reg_section = _winreg.HKEY_LOCAL_MACHINE
  608.     reg_key = 'Software\\SpamExperts'
  609.     sys_dir = win32api.GetSystemDirectory()
  610.     update_lists = ('general.txt', 'lsps.txt', 'sysfiles.txt', 'modules.txt')
  611.     if get_code():
  612.         base_url = 'http://' + options[('globals', 'professional_update_server')]
  613.     else:
  614.         base_url = 'http://' + options[('globals', 'software_update_server')]
  615.     
  616.     def __init__(self, tray = None):
  617.         self.exe_dir = application_directory()
  618.         self.LSP = LSPControl()
  619.         self.tray = tray
  620.         self.temp_dir = os.path.join(win32api.GetTempPath(), 'spamexperts_update')
  621.         
  622.         try:
  623.             os.makedirs(self.temp_dir)
  624.         except OSError:
  625.             pass
  626.  
  627.  
  628.     
  629.     def read_key(self, name):
  630.         key = _winreg.CreateKey(self.reg_section, self.reg_key)
  631.         
  632.         try:
  633.             return _winreg.QueryValueEx(key, name)[0]
  634.         except WindowsError:
  635.             pass
  636.  
  637.  
  638.     
  639.     def write_key(self, name, value):
  640.         if not isinstance(value, types.StringTypes):
  641.             raise AssertionError, 'Can only write strings.'
  642.         key = _winreg.CreateKey(self.reg_section, self.reg_key)
  643.         _winreg.SetValueEx(key, name, 0, _winreg.REG_SZ, value)
  644.  
  645.     
  646.     def emergency_check(self):
  647.         import wx
  648.         if get_code():
  649.             base_url = 'http://' + options[('globals', 'professional_update_server')]
  650.         else:
  651.             base_url = 'http://' + options[('globals', 'software_update_server')]
  652.         
  653.         try:
  654.             url = urllib2.urlopen(urlparse.urljoin(base_url, 'emergency.txt'))
  655.             text = url.read()
  656.         except (urllib2.HTTPError, urllib2.URLError, socket.error):
  657.             e = None
  658.             if hasattr(e, 'code') and e.code == 404:
  659.                 pass
  660.             else:
  661.                 print >>sys.stderr, "Couldn't check for emergency text", str(e)
  662.             return False
  663.  
  664.         initial_text = 'SpamExperts Emergency'
  665.         notice_text = 'SpamExperts Notice'
  666.         if text.lower().startswith(initial_text.lower()):
  667.             text = text[len(initial_text):].strip()
  668.             result = True
  669.         elif text.lower().startswith(notice_text.lower()):
  670.             text = text[len(notice_text):].strip()
  671.             result = False
  672.         else:
  673.             return False
  674.         flags = wx.OK | wx.ICON_INFORMATION
  675.         d = wx.MessageDialog(None, text, self.title, flags)
  676.         d.ShowModal()
  677.         d.Destroy()
  678.         return result
  679.  
  680.     
  681.     def conclude_update_needed(self):
  682.         if self.read_key('update_in_progress') == 'True':
  683.             return True
  684.         
  685.         
  686.         self.callback = lambda x, y: (x, y)
  687.         general = UpdateGeneral(self.exe_dir)
  688.         modules = UpdateModules(self.exe_dir)
  689.         self.cleanup((), general, general, general, modules)
  690.         return False
  691.  
  692.     
  693.     def conclude_update(self, callback = None):
  694.         if not callback:
  695.             print 'Bad call to conclude update.  Aborting.'
  696.             return None
  697.         
  698.         if options[('globals', 'verbose')]:
  699.             print 'Concluding update'
  700.         
  701.         if self.read_key('run_copyfile') == 'True':
  702.             if options[('globals', 'verbose')]:
  703.                 print 'Copyfile has not run; retrying.'
  704.             
  705.             in_use_fn = os.path.join(tempfile.gettempdir(), 'spamexperts_update', 'in_use.txt')
  706.             subprocess.Popen('copyfile.exe "' + in_use_fn + '"', shell = True, cwd = self.exe_dir)
  707.             return None
  708.         
  709.         if self.read_key('resume_LSP_install') == 'True':
  710.             if options[('globals', 'verbose')]:
  711.                 print 'Registering LSP'
  712.             
  713.             self.LSP.installLSP()
  714.             self.write_key('resume_LSP_install', 'False')
  715.         
  716.         self.callback = callback
  717.         general = UpdateGeneral(self.exe_dir)
  718.         modules = UpdateModules(self.exe_dir)
  719.         self.write_build_number(self.base_url)
  720.         self.cleanup(self.update_lists, general, general, general, modules)
  721.         self.notify_success()
  722.         self.write_key('update_in_progress', 'False')
  723.  
  724.     
  725.     def download_txt_file(self, remote_file, local_file):
  726.         import wx
  727.         while True:
  728.             
  729.             try:
  730.                 url = urllib2.urlopen(remote_file)
  731.                 content = url.read()
  732.             except (urllib2.HTTPError, urllib2.URLError, socket.error):
  733.                 e = None
  734.  
  735.             if '<html>' not in content.lower():
  736.                 file(local_file, 'wb').write(content)
  737.                 return True
  738.             
  739.             flags = wx.YES_NO | wx.ICON_QUESTION
  740.             d = wx.MessageDialog(None, _('File download failed.  Retry?'), self.title, flags)
  741.             result = d.ShowModal()
  742.             d.Destroy()
  743.             if result == wx.ID_NO:
  744.                 return False
  745.                 continue
  746.  
  747.     
  748.     def update(self, callback):
  749.         import wx
  750.         if self.emergency_check():
  751.             return None
  752.         
  753.         self.write_key('update_in_progress', 'True')
  754.         self.callback = callback
  755.         
  756.         self.general_callback = lambda i, s: callback(5 + i * 0.34999999999999998, s)
  757.         
  758.         self.lsp_callback = lambda i, s: callback(40 + i * 0.074999999999999997, s)
  759.         
  760.         self.external_callback = lambda i, s: callback(47.5 + i * 0.074999999999999997, s)
  761.         
  762.         self.modules_callback = lambda i, s: callback(55 + i * 0.29999999999999999, s)
  763.         self.reboot_required = False
  764.         self.restart_required = False
  765.         general = UpdateGeneral(self.exe_dir)
  766.         lsp = UpdateGeneral(self.exe_dir)
  767.         external = UpdateGeneral(self.exe_dir)
  768.         modules = UpdateModules(self.exe_dir)
  769.         update_lists = self.update_lists
  770.         base_url = self.base_url
  771.         self.callback(1, _('Downloading update list'))
  772.         print 'Downloading update from', base_url
  773.         for i, update_list in enumerate(update_lists):
  774.             self.callback(1 + 4 * i / len(update_lists), _('Downloading %s') % (update_list,))
  775.             remote_file = urlparse.urljoin(base_url, update_list)
  776.             local_temp = os.path.join(self.temp_dir, update_list)
  777.             if not self.download_txt_file(remote_file, local_temp):
  778.                 return None
  779.                 continue
  780.         
  781.         self.callback(5, _('Update list download complete'))
  782.         general_replaced = self.process_list(general, update_lists[0], self.general_callback, self.exe_dir)
  783.         if options[('globals', 'verbose')]:
  784.             print 'General replacement: ', general_replaced
  785.         
  786.         if general_replaced is None:
  787.             return None
  788.         
  789.         lsps_replaced = self.process_list(lsp, update_lists[1], self.lsp_callback, self.sys_dir)
  790.         if options[('globals', 'verbose')]:
  791.             print 'LSPS replacement: ', lsps_replaced
  792.         
  793.         if lsps_replaced is None:
  794.             return None
  795.         
  796.         external_replaced = self.process_list(external, update_lists[2], self.external_callback, self.exe_dir)
  797.         if options[('globals', 'verbose')]:
  798.             print 'External replacement: ', external_replaced
  799.         
  800.         if external_replaced is None:
  801.             return None
  802.         
  803.         zip_fn = os.path.join(self.exe_dir, 'lib', 'spamexperts.modules')
  804.         modules_replaced = self.process_list(modules, update_lists[3], self.modules_callback, zip_fn)
  805.         if options[('globals', 'verbose')]:
  806.             print 'Modules replacement: ', modules_replaced
  807.         
  808.         if modules_replaced is None:
  809.             return None
  810.         
  811.         self.callback(87.5, _('Replacing files...'))
  812.         result = self.do_replace(general)
  813.         if result is None:
  814.             general.undo()
  815.             return None
  816.         
  817.         self.callback(89, _('Replacing files...'))
  818.         result = self.do_replace(lsp)
  819.         if result is None:
  820.             general.undo()
  821.             lsp.undo()
  822.             return None
  823.         
  824.         self.callback(91, _('Replacing files...'))
  825.         result = self.do_replace(external)
  826.         if result is None:
  827.             general.undo()
  828.             lsp.undo()
  829.             external.undo()
  830.             return None
  831.         
  832.         self.callback(92.5, _('Replacing modules...'))
  833.         self.do_replace(modules)
  834.         if result is None:
  835.             general.undo()
  836.             lsp.undo()
  837.             external.undo()
  838.             modules.undo()
  839.             return None
  840.         
  841.         if lsps_replaced == general.SUCCEEDED or lsps_replaced == general.RESTART:
  842.             self.reboot_required = True
  843.             if options[('globals', 'verbose')]:
  844.                 print 'Unregistering LSP'
  845.             
  846.             self.LSP.removeLSP()
  847.             self.write_key('resume_LSP_install', 'True')
  848.             if options[('globals', 'verbose')]:
  849.                 print 'Rebooting'
  850.             
  851.             self.reboot()
  852.             return None
  853.         
  854.         if self.restart_required:
  855.             self.callback(100, _('Restarting updater to replace in-use files.'))
  856.             msg = _('SpamExperts must restart in order to complete this update. Please finish adjusting settings or classifying messages and exit SpamExperts (the application will automatically restart).')
  857.             if hasattr(self, 'balloon'):
  858.                 self.balloon(msg, subprocess.Popen, ('copyfile.exe "' + self._in_use_fn + '"',), {
  859.                     'shell': True,
  860.                     'cwd': self.exe_dir })
  861.             else:
  862.                 print >>sys.stderr, "Couldn't present update balloon."
  863.                 subprocess.Popen('copyfile.exe "' + self._in_use_fn + '"', shell = True, cwd = self.exe_dir)
  864.             return None
  865.         
  866.         self.write_build_number(base_url)
  867.         self.cleanup(update_lists, general, lsp, external, modules)
  868.         self.notify_success()
  869.  
  870.     
  871.     def cleanup(self, update_lists, general, lsp, external, modules):
  872.         self.callback(97, _('Cleaning up after update.'))
  873.         for update_list in update_lists:
  874.             
  875.             try:
  876.                 os.remove(os.path.join(self.temp_dir, update_list))
  877.             continue
  878.             except OSError:
  879.                 continue
  880.             
  881.  
  882.         
  883.         general.cleanup()
  884.         lsp.cleanup()
  885.         external.cleanup()
  886.         modules.cleanup()
  887.  
  888.     
  889.     def write_build_number(self, base_url):
  890.         self.callback(99, _('Registering updated version.'))
  891.         remote_file = urlparse.urljoin(base_url, 'version.txt')
  892.         local_file = os.path.join(self.temp_dir, 'version.txt')
  893.         timeout = socket.getdefaulttimeout()
  894.         socket.setdefaulttimeout(15)
  895.         if not self.download_txt_file(remote_file, local_file):
  896.             socket.setdefaulttimeout(timeout)
  897.             return None
  898.         
  899.         socket.setdefaulttimeout(timeout)
  900.         version = file(local_file).read().strip()
  901.         urllib.urlcleanup()
  902.         self.write_key('LatestBuild', version)
  903.  
  904.     
  905.     def notify_success(self):
  906.         msg = _('%s has been updated successfully.') % (self.title,)
  907.         self.callback(100, msg)
  908.         self.write_key('update_in_progress', 'False')
  909.  
  910.     
  911.     def _reattempt(self, func, args, failed, restart):
  912.         import wx
  913.         while True:
  914.             result = func(*args)
  915.             if result == failed:
  916.                 flags = wx.YES_NO | wx.ICON_QUESTION
  917.                 d = wx.MessageDialog(None, _('File replacement failed. Retry?'), self.title, flags)
  918.                 result = d.ShowModal()
  919.                 d.Destroy()
  920.                 if result == wx.ID_NO:
  921.                     return None
  922.                 
  923.             result == wx.ID_NO
  924.             if result == restart:
  925.                 self.restart_required = True
  926.             
  927.             break
  928.         return result
  929.  
  930.     
  931.     def in_use_file(self):
  932.         if hasattr(self, '_in_use_fn'):
  933.             first_open = False
  934.             f = file(self._in_use_fn, 'a')
  935.         else:
  936.             first_open = True
  937.             self._in_use_fn = os.path.join(win32api.GetTempPath(), 'spamexperts_update', 'in_use.txt')
  938.             f = file(self._in_use_fn, 'w')
  939.             self.write_key('run_copyfile', 'True')
  940.         if first_open and self.reboot_required:
  941.             if options[('globals', 'verbose')]:
  942.                 print 'Files will be replaced on reboot.'
  943.             
  944.             f.write('\n')
  945.         elif first_open:
  946.             if options[('globals', 'verbose')]:
  947.                 print 'Files will be replaced on application restart.'
  948.             
  949.             f.write('"%s"\n' % (sys.executable,))
  950.         
  951.         return f
  952.  
  953.     
  954.     def do_replace(self, updater):
  955.         result = self._reattempt(updater.replace_files, (), updater.FAILED, updater.RESTART)
  956.         if updater.replace_on_restart:
  957.             f = self.in_use_file()
  958.             for old, new in updater.replace_on_restart:
  959.                 f.write('%s\n%s\n' % (new, old))
  960.             
  961.             f.close()
  962.             self.restart_required = True
  963.         
  964.         return result
  965.  
  966.     
  967.     def process_list(self, updater, update_list, callback, directory):
  968.         return self._reattempt(updater.download_files, (os.path.join(self.temp_dir, update_list), callback, directory), updater.FAILED, updater.RESTART)
  969.  
  970.     
  971.     def start_on_reboot(name, command):
  972.         if options[('globals', 'verbose')]:
  973.             print 'Executing on restart:', command
  974.         
  975.         key = _winreg.CreateKey(_winreg.HKEY_LOCAL_MACHINE, 'Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce')
  976.         _winreg.SetValueEx(key, name, 0, _winreg.REG_SZ, command)
  977.  
  978.     start_on_reboot = staticmethod(start_on_reboot)
  979.     
  980.     def reboot(self):
  981.         import wx
  982.         self.write_key('need_reboot', 'True')
  983.         if self.restart_required:
  984.             cmd = os.path.join(self.exe_dir, 'copyfile.exe ' + self._in_use_fn)
  985.         
  986.         self.start_on_reboot('spamexperts_update', cmd)
  987.         flags = wx.YES_NO | wx.ICON_QUESTION
  988.         msg = _('%s must restart your computer for this update to be completed.\r\n%s will re-launch when Windows boots up next.\r\nWould you like to restart now?') % (self.name, self.name)
  989.         d = wx.MessageDialog(None, msg, self.title, flags)
  990.         result = d.ShowModal()
  991.         d.Destroy()
  992.         if result == wx.ID_OK:
  993.             Reboot()
  994.         
  995.  
  996.  
  997. if __name__ == '__main__':
  998.     v = VersionControl()
  999.     print v.getVersionFromServer()
  1000.  
  1001.